//
//  main.cpp
//  sphere
//
//  Created by Scott Friedman on 10/10/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#include <iostream>
#include "Angel.h"
#ifdef __APPLE__  // include Mac OS X verions of headers
#  include <OpenGL/OpenGL.h>
#  include <GLUT/glut.h>
#else // non-Mac OS X operating systems
#  include <glew.h>
#  include <freeglut.h>
#  include <freeglut_ext.h>
#endif  // __APPLE__

#include "Sphere.h"
#include "Camera.h"
#include "Keyboard.h"
#include <map>

using namespace std;

const int NUM_PLANETS = 6;

//----

//GLuint ModelView, Projection;

CSphere* planets[NUM_PLANETS];

CSphere* attached = NULL;

color4 material_ambient[NUM_PLANETS];
color4 material_diffuse[NUM_PLANETS];
color4 material_specular[NUM_PLANETS];
GLfloat material_shininess[NUM_PLANETS];
color4 ambient_product[NUM_PLANETS];
color4 diffuse_product[NUM_PLANETS];
color4 specular_product[NUM_PLANETS];
point4 light_position;

CCamera camera;
CKeyboard keys;

GLuint program;
GLuint origintranslation;
GLuint modelview;
GLuint modeltranslation;
GLuint modelrotation;
GLuint projection;
GLuint type;
GLuint lightposition;
GLuint ambientproduct;
GLuint diffuseproduct;
GLuint specularproduct;
GLuint shininess;

GLfloat fovy; // Field-of-view in Y direction angle (in degrees)
GLfloat aspect; // Viewport aspect ratio
GLfloat zNear;
GLfloat zFar;

vec3 origin(1,1,1);
vec3 initSunPos(1,1,1);
vec3 initIcePlanetPos(250,1,1);
vec3 initSwampPlanetPos(100,1,1);
vec3 initWaterPlanetPos(150,1,1);
vec3 initMudPlanetPos(200,1,1);

//----

void init( void )
{
	planets[0] = new CSphere(initSunPos, 25, 0, GOURAND, HIGH);	// sun
	planets[1] = new CSphere(initIcePlanetPos, 5, 1, FLAT, MED_LOW);	// ice
	planets[2] = new CSphere(initSwampPlanetPos, 8, 7, GOURAND, MED_LOW);	// swamp
	planets[3] = new CSphere(initWaterPlanetPos, 15, 10, PHONG, HIGH);	// water
	planets[4] = new CSphere(initMudPlanetPos, 12, 3, FLAT, MED_HIGH);	// mud
	planets[5] = new CSphere(planets[3], 30, 3, 13, FLAT, MED_HIGH);	// moon

	camera.InitCamera();
	zNear = 1; zFar = 400; fovy = 45.0;
    
    // create vertex array object
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // create and initialize a buffer object
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);

	// calculate total size
	int totalSize = 0;
	for (int i = 0; i < NUM_PLANETS; i++)
	{
		totalSize += planets[i]->GetPositionDataSize() + planets[i]->GetNormalDataSize();
	}

	glBufferData(GL_ARRAY_BUFFER, totalSize, NULL, GL_STATIC_DRAW);

	int positionDataEnd = 0;
	for (int i = 0; i < NUM_PLANETS; i++)
	{
		int size = planets[i]->GetPositionDataSize();
		glBufferSubData(GL_ARRAY_BUFFER, positionDataEnd, size, planets[i]->GetPositionData());
		positionDataEnd += size;
	}

	int normalDataEnd = positionDataEnd;
	for (int i = 0; i < NUM_PLANETS; i++)
	{
		int size = planets[i]->GetNormalDataSize();
		glBufferSubData(GL_ARRAY_BUFFER, normalDataEnd, size, planets[i]->GetNormalData());
		normalDataEnd += size;
	}

    // load shaders and use the resulting shader programs
	program = InitShader("vshader.glsl", "fshader.glsl");
	glUseProgram(program);

	origintranslation = glGetUniformLocation(program, "OriginTranslation");
	modelview = glGetUniformLocation(program, "ModelView");
	modeltranslation = glGetUniformLocation(program, "ModelTranslation");
	modelrotation = glGetUniformLocation(program, "ModelRotation");
	projection = glGetUniformLocation(program, "Projection");
	type = glGetUniformLocation(program, "ShadingType");
	lightposition = glGetUniformLocation(program, "LightPosition");
	ambientproduct = glGetUniformLocation(program, "AmbientProduct");
	diffuseproduct = glGetUniformLocation(program, "DiffuseProduct");
	specularproduct = glGetUniformLocation(program, "SpecularProduct");
	shininess = glGetUniformLocation(program, "Shininess");

    // setup flat shader vertex arrays
    GLuint vPosition = glGetAttribLocation(program, "vPosition");
    glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
	glEnableVertexAttribArray(vPosition);
	
    GLuint vNormal = glGetAttribLocation(program, "vNormal");
    glVertexAttribPointer(vNormal, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(positionDataEnd));
	glEnableVertexAttribArray(vNormal);
    
    // initialize shader lighting parameters
	// light position is distance from origin
    light_position = point4( 0., 0., 0., 0. );
	color4 light_ambient( 0.2, 0.2, 0.2, 1.0 );
	color4 light_diffuse( 1.0, 1.0, 1.0, 1.0 );
	color4 light_specular( 1.0, 1.0, 1.0, 1.0 );

	// SUN planent
	material_ambient[0] = color4( 0.5, 0.0, 0.0, 1.0 );
	material_diffuse[0] = color4( 0.5, 0.0, 0.0, 1.0 );
	material_specular[0] = color4( 0.8, 0.0, 0.1, 1.0 );
	material_shininess[0] = 80.f;

	// ICE planent
    material_ambient[1] = color4(0.7, 0.7, 0.7, 1.0 );
    material_diffuse[1] = color4( 0.7, 0.7, 0.7, 1.0 );
    material_specular[1] = color4( 0.7, 0.7, 0.7, 1.0 );
    material_shininess[1] = 50.f;

	// SWAMP planent
    material_ambient[2] = color4( 0.1, 0.5,	0.3, 1.0 );
    material_diffuse[2] = color4( 0.2, 0.9,	0.3, 1.0);
    material_specular[2] = color4( 0.1, 0.6, 0.3, 1.0 );
    material_shininess[2] = 15.;

	// WATER planent
    material_ambient[3] = color4( 0.3, 0.5, .7, 1.0 );
    material_diffuse[3] = color4( 0.3, 0.5, .7, 1.0 );
    material_specular[3] = color4( 0.3, 0.5, .7, 1.0 );
    material_shininess[3] = 12.8;

	// MUD planent
    material_ambient[4] = color4( .5, .3, .1, 1.0 );
    material_diffuse[4] = color4( .5, .3, .1, 1.0f );
    material_specular[4] = color4( 0.0f, 0.0f, 0.0f, 1.0f );
    material_shininess[4] = 5.f;

	// Moon planent
    material_ambient[5] = color4( .3, .3, .5, 1.0 );
    material_diffuse[5] = color4( .5, .3, .1, 1.0f );
    material_specular[5] = color4( 0.8f, 0.8f, 0.8f, 1.0f );
    material_shininess[5] = 75.f;
    
	for (int i = 0; i < NUM_PLANETS; i++)
	{
		ambient_product[i] = light_ambient * material_ambient[i];
		diffuse_product[i] = light_diffuse * material_diffuse[i];
		specular_product[i] = light_specular * material_specular[i];
	}

	glUniform4fv(lightposition, 1, light_position);
	glUniformMatrix4fv(origintranslation, 1, GL_TRUE, Translate(origin));
    
    glEnable( GL_DEPTH_TEST );
    
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	//glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}

void display( void )
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	mat4 proj = Perspective(70.0f, aspect, zNear, zFar);
	glUniformMatrix4fv( projection, 1, GL_TRUE, proj );
	
	int count = 0;
	for (int i = 0; i < NUM_PLANETS; i++)
	{
		// retrieve transformation uniform variable locations
		glUniform1i(type, planets[i]->GetShadingType());
		glUniform4fv(ambientproduct, 1, ambient_product[i]);
		glUniform4fv(diffuseproduct, 1, diffuse_product[i]);
		glUniform4fv(specularproduct, 1, specular_product[i]);
		glUniform1fv(shininess, 1, &material_shininess[i]);

		glUniformMatrix4fv( modelrotation, 1, GL_TRUE, planets[i]->GetRotationMatrix() );
		glUniformMatrix4fv( modeltranslation, 1, GL_TRUE, planets[i]->GetTranslationMatrix() );
		glUniformMatrix4fv( modelview, 1, GL_TRUE, camera.GetViewMatrix() );

		glDrawArrays(GL_TRIANGLES, count, planets[i]->GetNumVertices());
		count += planets[i]->GetNumVertices();
	}

	glutSwapBuffers();
}

void reshape( int width, int height )
{
    glViewport(0, 0, width, height);
    aspect = GLfloat(width)/height;
	glutPostRedisplay();
}

void performKeyboardActions()
{
	vector<int> keysPressed = keys.GetKeysPressedDown();

	for (vector<int>::iterator it = keysPressed.begin(); it != keysPressed.end(); it++)
	{
		switch (*it)
		{
		case 'x':
			light_position.x -= 1;
		case 'X':
			light_position.x += 1;
		case '1':
		case '2':
		case '3':
		case '4':
		case GLUT_KEY_UP:
			if (!attached)
			{
				camera.PitchUp(1);
			}
			break;
		case GLUT_KEY_DOWN:
			if (!attached)
			{
				camera.PitchUp(-1);
			}
			break;
		case GLUT_KEY_RIGHT:
			camera.HeadRight(1);
			break;
		case GLUT_KEY_LEFT:
			camera.HeadRight(-1);
			break;
		case 033: // Escape Key
		case 'q': 
		case 'Q':
			keys.ReleaseKey(*it);  // don't allow key to be held down
			exit( EXIT_SUCCESS );
			break;
		case 'w':
		case 'W':
			// move forward
			if (!attached)
			{
				camera.MoveForward( 2 );
			}
			break;
		case 'r':
		case 'R':
			// move backward
			if (!attached)
			{
				camera.MoveForward( -2 );
			}
			break;
		case 's':
		case 'S':
			// move right
			if (!attached)
			{
				camera.StrafeRight( 2 );
			}
			break;
		case 'a':
		case 'A':
			// move left
			if (!attached)
			{
				camera.StrafeRight( -2 );
			}
			break;
		case 'n':
		case 'N':
			// make horizontal field of view narrower
			fovy -= 5;
			keys.ReleaseKey(*it);  // don't allow key to be held down
			break;
		case 'b':
		case 'B':
			// make horizontal field of view wider
			fovy += 5;
			keys.ReleaseKey(*it);  // don't allow key to be held down
			break;
		case ' ':
			// reset values to their defaults
			camera.ResetCameraParameters();
			fovy = 45.0;
			zNear = 0.5;
			zFar = 400;
			attached = NULL;
			keys.ReleaseKey(*it);  // don't allow key to be held down
			break;
		}
	}

	glutPostRedisplay();
}

void idle()
{
	glutPostRedisplay();
}

void special(int key, int x, int y)
{
	keys.PressKey(key);

	performKeyboardActions();
}

void specialUp(int key, int x, int y)
{
	keys.ReleaseKey(key);

	performKeyboardActions();
}

void keyboard( unsigned char key, int x, int y )
{
	keys.PressKey(key);

	performKeyboardActions();
}

void keyboardUp( unsigned char key, int x, int y )
{
	keys.ReleaseKey(key);

	performKeyboardActions();
}

int main (int argc, char *argv[] )
{
    // insert code here...
    std::cout << "Hello, World!\n";
    glutInit( &argc, argv );
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 50);
	glutInitWindowSize(800, 600);
    glutCreateWindow( "Sphere" );
    
	glewInit( );
	
	init( );
    
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
	glutKeyboardUpFunc(keyboardUp);
	glutSpecialFunc(special);
	glutSpecialUpFunc(specialUp);
	glutIdleFunc(idle);
    
    glutMainLoop();
    return 0;
}

